#include "SQLSelectItem.h"

#include "../expr/SQLIdentifierExpr.h"
#include "../expr/SQLPropertyExpr.h"
#include "../../../utils/instanceof.h"
#include "../../../utils/FnvHash.h"
#include "../../SQLUtils.h"
#include "../../visitor/SQLASTVisitor.h"

#include "SQLSubqueryTableSource.h"

SETCALSS(SQLSelectItem);

SQLSelectItem::SQLSelectItem()
{
  aliasList = std::make_shared<std::list<string_ptr>>();
}

SQLSelectItem::SQLSelectItem(SQLExpr_ptr expr)
    : SQLSelectItem(expr, make_string_ptr(""))
{
}

SQLSelectItem::SQLSelectItem(int value)
    : SQLSelectItem(SQLExpr_ptr(new SQLIntegerExpr(value)), make_string_ptr(""))
{
}

SQLSelectItem::SQLSelectItem(SQLExpr_ptr expr, string_ptr alias)
{
  this->expr = expr;
  this->alias = alias;

  if (expr != NULL)
  {
    expr->setParent(SharedObject(SQLSelectItem));
  }
  aliasList = std::make_shared<std::list<string_ptr>>();
}

SQLSelectItem::SQLSelectItem(SQLExpr_ptr expr, string_ptr alias, bool connectByRoot)
{
  this->connectByRoot = connectByRoot;
  this->expr = expr;
  this->alias = alias;

  if (expr != NULL)
  {
    // expr->setParent(SharedObject(SQLSelectItem));
    expr->setParent(SharedObject(SQLSelectItem));
  }
  aliasList = std::make_shared<std::list<string_ptr>>();
}

SQLSelectItem::SQLSelectItem(SQLExpr_ptr expr, string_list_ptr aliasList, bool connectByRoot)
{
  this->connectByRoot = connectByRoot;
  this->expr = expr;
  this->aliasList = aliasList;

  if (expr != NULL)
  {
    // expr->setParent(SharedObject(SQLSelectItem));
    expr->setParent(SharedObject(SQLSelectItem));
  }
}

SQLExpr_ptr SQLSelectItem::getExpr()
{
  return this->expr;
}

void SQLSelectItem::setExpr(SQLExpr_ptr expr)
{
  if (expr != NULL)
  {
    expr->setParent(SharedObject(SQLSelectItem));
  }
  this->expr = expr;
}

void SQLSelectItem::setExpr(SQLObject_ptr expr)
{
  if (expr != nullptr)
  {
    expr->setParent(SharedObject(SQLSelectItem));
  }
  this->expr = std::dynamic_pointer_cast<SQLExpr>(expr);
}

string_ptr SQLSelectItem::computeAlias()
{
  string_ptr alias = this->getAlias();
  if (alias->empty())
  {
    if (instanceof <SQLExpr, SQLIdentifierExpr>(expr))
    {
      alias = (std::dynamic_pointer_cast<SQLIdentifierExpr>(expr))->getName();
    }
    else if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
    {
      alias = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getName();
    }
  }

  return SQLUtils::normalize(alias);
}

SQLDataType_ptr SQLSelectItem::computeDataType()
{
  if (expr == nullptr)
  {
    return nullptr;
  }

  return expr->computeDataType();
}

string_ptr SQLSelectItem::getAlias()
{
  return this->alias;
}

string_ptr SQLSelectItem::getAlias2()
{
  if (this->alias->empty() || this->alias->length() == 0)
  {
    return alias;
  }

  char first = alias->at(0);
  if (first == '"' || first == '\'')
  {
    string_ptr chars = make_string_ptr("");
    int len = 0;
    for (int i = 1; i < alias->length() - 1; ++i)
    {
      char ch = alias->at(i);
      if (ch == '\\')
      {
        ++i;
        ch = alias->at(i);
      }
      chars->push_back(ch);
    }
    return chars;
  }

  return alias;
}

void SQLSelectItem::setAlias(string_ptr alias)
{
  this->alias = alias;
}

// void output(Appendable buf) {
//     try {
//         if (this->connectByRoot) {
//             buf.append(" CONNECT_BY_ROOT ");
//         }
//         this->expr->output(buf);
//         if ((this->alias != NULL) && (this->alias->length() != 0)) {
//             buf.append(" AS ");
//             buf.append(this->alias);
//         }
//     } catch (IOException ex) {
//         throw new FastsqlException("output error", ex);
//     }
// }

void SQLSelectItem::accept0(SQLASTVisitor_ptr v)
{
  if (v->visit(SharedObject(SQLSelectItem)))
  {
    if (expr != NULL)
    {
      expr->accept(v);
    }
  }
  v->endVisit(SharedObject(SQLSelectItem));
}

// BOOL_ptr equals(Object_ptr  o) {
//     if (this == o) {
//         return BOOL::TRUE;
//     }
//     if (o == NULL || getClass() != o->getClass()) {
//         return BOOL::FALSE;
//     }

//     SQLSelectItem* that = (SQLSelectItem_ptr ) o;

//     if (connectByRoot != that->connectByRoot) {
//         return BOOL::FALSE;
//     }
//     if (alias_hash() != that->alias_hash()) {
//         return BOOL::FALSE;
//     }
//     if (expr != NULL ? !expr->equals(that->expr) : that->expr != NULL) {
//         return BOOL::FALSE;
//     }

//     return aliasList != NULL
//             ? aliasList->equals(that->aliasList)
//             : that->aliasList == NULL;
// }

// int hashCode() {
//     int result = expr != NULL ? expr->hashCode() : 0;
//     result = 31 * result + (!alias->empty() ? alias->hashCode() : 0);
//     result = 31 * result + (connectByRoot ? 1 : 0);
//     result = 31 * result + (int) (alias_hash() ^ (alias_hash() >>> 32));
//     result = 31 * result + (aliasList != NULL ? aliasList.hashCode() : 0);
//     return result;
// }

bool SQLSelectItem::isConnectByRoot()
{
  return connectByRoot;
}

void SQLSelectItem::setConnectByRoot(bool connectByRoot)
{
  this->connectByRoot = connectByRoot;
}

// SQLSelectItem_ptr SQLSelectItem::clone()
SQLObject_ptr SQLSelectItem::clone()
{
  SQLObject_ptr clone_tmp = SQLSelectItem_ptr(new SQLSelectItem());
  SQLSelectItem_ptr clone_res = std::dynamic_pointer_cast<SQLSelectItem>(clone_tmp);

  clone_res->alias = alias;
  if (expr != nullptr)
  {
    clone_res->setExpr(expr->clone());
  }
  clone_res->connectByRoot = connectByRoot;
  if (aliasList != nullptr)
  {
    clone_res->aliasList = aliasList;
  }
  return clone_res;
}

BOOL_ptr SQLSelectItem::replace(SQLExpr_ptr expr, SQLExpr_ptr target)
{
  if (this->expr == expr)
  {
    setExpr(target);
    return BOOL::TRUE;
  }

  return BOOL::FALSE;
}

BOOL_ptr SQLSelectItem::match(string_ptr alias)
{
  if (alias->empty())
  {
    return BOOL::FALSE;
  }

  long hash = FnvHash::hashCode64(alias);
  return match(hash);
}

long SQLSelectItem::alias_hash()
{
  if (this->aliasHashCode64 == 0)
  {
    this->aliasHashCode64 = FnvHash::hashCode64(alias);
  }
  return aliasHashCode64;
}

BOOL_ptr SQLSelectItem::match(long alias_hash_)
{
  long hash = alias_hash();

  if (hash == alias_hash_)
  {
    return BOOL::TRUE;
  }

  // if (instanceof <SQLExpr, SQLAllColumnExpr>(expr)) {
  //     SQLTableSource_ptr  resolvedTableSource = (dynamic_cast<SQLAllColumnExpr *>(expr))->getResolvedTableSource();
  //     if (resolvedTableSource != NULL && resolvedTableSource->findColumn(alias_hash_) != NULL) {
  //         return BOOL::TRUE;
  //     }
  //     return BOOL::FALSE;
  // }

  if (instanceof <SQLExpr, SQLIdentifierExpr>(expr))
  {
    if ((std::dynamic_pointer_cast<SQLIdentifierExpr>(expr))->nameHashCode64() == alias_hash_)
    {
      return BOOL::TRUE;
    }
    else
    {
      return BOOL::FALSE;
    }
  }

  if (instanceof <SQLExpr, SQLPropertyExpr>(expr))
  {
    string_ptr ident = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getName();
    if ("*" == *ident)
    {
      SQLTableSource_ptr resolvedTableSource = (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->getResolvedTableSource();
      if (resolvedTableSource == NULL)
      {
        return BOOL::FALSE;
      }

      BOOL_ptr isParentTableSource = BOOL::FALSE;
      if (instanceof <SQLTableSource, SQLSubqueryTableSource>(resolvedTableSource))
      {
        for (SQLObject_ptr parent = this->getParent(); parent != NULL; parent = parent->getParent())
        {
          if (parent == resolvedTableSource)
          {
            isParentTableSource = BOOL::TRUE;
            break;
          }
        }
      }

      // return (!isParentTableSource) && resolvedTableSource->findColumn(alias_hash_) != NULL;
    }

    if (alias->empty() && (std::dynamic_pointer_cast<SQLPropertyExpr>(expr))->nameHashCode64() == alias_hash_)
    {
      return BOOL::TRUE;
    }
    else
    {
      return BOOL::FALSE;
    }
  }

  return BOOL::FALSE;
}

string_list_ptr SQLSelectItem::getAliasList()
{
  return aliasList;
}

// string_ptr  toString() {
//     DbType dbType = NULL;
//     if (parent instanceof OracleSQLObject) {
//         dbType = DbType.oracle;
//     }
//     return SQLUtils.toSQLString(this, dbType);
// }

// BOOL_ptr isUDTFSelectItem() {
//     return aliasList != NULL && aliasList.size() > 0;
// }
